﻿using System;
using System.Linq;
using System.Collections.Generic;
using OverStore.Runtime.OverStoreContext;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.Storage;
using OverStore.Exceptions;
using OverStore.Sql.TableSchema;
using OverStore.Runtime.ObjectRepository.ChangesTracker;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using System.Reflection;
using OverStore.Sql;
using System.Globalization;
using OverStore.Runtime.ObjectRepository.Attacher;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Runtime.ObjectRepository;
using OverStore.Runtime.ObjectRepository.Depot.IdentifierGenerator;
using OverStore.Runtime.ObjectRepository.ErrorTransformer;
using OverStore.Runtime.ObjectRepository.Reader;
using OverStore.Configuration.ObjectRepository.ErrorTransformer;
using OverStore.Configuration.ObjectRepository.Mapping;
using OverStore.Configuration.ObjectRepository.CommandSet;
using OverStore.Configuration.ObjectRepository.Attacher;
using OverStore.Configuration.ObjectRepository.Depot;
using OverStore.Configuration.ObjectRepository.ChangesTracker;
using OverStore.Configuration.ObjectRepository.CallbackManager;

namespace OverStore.Configuration.ObjectRepository
{
    /// <summary>
    /// Repository configuration consists of mapping each column to instance data.
    /// </summary>
    public class TableMappingRepositoryConfiguration<TInstance, TIdentifier> : IComponentConfiguration<IRepository<TInstance>>,
        IComponentConfiguration<IRepositoryBase>
        where TInstance : class
    {
        private readonly List<IComponentConfiguration<ICallbackManager<TInstance>>> _Callbacks = new List<IComponentConfiguration<ICallbackManager<TInstance>>>();
        private List<IMapping<TInstance>> _Mappings = new List<IMapping<TInstance>>();
        private Dictionary<string, IMapping<TInstance>> _MappingsMap;
        private PrimaryKeyMapping<TInstance, TIdentifier> _PrimaryKey;
        private MatchErrorTransformerConfiguration<TInstance> _ErrorTransformer;
        private SqlFormatting _SqlFormatting = new SqlServerCeFormatting();
        private IComponentConfiguration<IAttacher<TInstance, TIdentifier>> _Attacher;
        private Table _Table;

        /// <summary>
        /// Creates the new instance of <see cref="IRepository{TInstance}"/> type using set of services provided by <paramref name="context"/> object.
        /// </summary>
        /// <param name="context">The context provides access to services shared between components.</param>
        /// <returns>
        /// The new instance of <see cref="IRepository{TInstance}"/> class.
        /// </returns>
        public IRepository<TInstance> CreateComponent(IOverStoreContext context)
        {
            var mapperConfig = CreateMapper();
            var depot = CreateDepot();
            var commandSet = CreateCommandSet();
            return new PersistenceObjectRepository<TInstance, TIdentifier>(
                context.PersistenceServiceProvider,
                CreateCallbackManager().CreateComponent(context),
                depot.CreateComponent(context),
                null,
                CreateChangesTracker().CreateComponent(context),
                commandSet.CreateComponent(context),
                context.DataStorage,
                mapperConfig.CreateComponent(context),
                _ErrorTransformer == null ? null : _ErrorTransformer.CreateComponent(context),
                (_Attacher ?? CreateAttacher()).CreateComponent(context),
                context.ScopeManager,
                ParameterlessReader == null ? null : ParameterlessReader.CreateComponent(context),
                ParameterizedReaders == null ? null : ParameterizedReaders.Select(r => r.CreateComponent(context)));
        }

        /// <summary>
        /// Gets or sets table name where instance of the type are stored.
        /// </summary>
        public string Table { get; set; }

        /// <summary>
        /// Gets or sets the list of mapping.
        /// </summary>
        public IEnumerable<IMapping<TInstance>> Mappings
        {
            get
            {
                return _Mappings;
            }
            set
            {
                if (Object.ReferenceEquals(value, null))
                    throw new ArgumentNullException("value");
                _Mappings = value.ToList();
            }
        }

        /// <summary>
        /// Gets or sets the parameterless reader.
        /// </summary>
        /// <value>The parameterless reader.</value>
        public IComponentConfiguration<IReader<TInstance, TIdentifier>> ParameterlessReader { get; set; }

        /// <summary>
        /// Gets or sets the parameterized readers.
        /// </summary>
        /// <value>The parameterized readers.</value>
        public IEnumerable<IComponentConfiguration<IReader<TInstance, TIdentifier>>> ParameterizedReaders { get; set; }

        IRepositoryBase IComponentConfiguration<IRepositoryBase>.CreateComponent(IOverStoreContext context)
        {
            return CreateComponent(context);
        }

        private TableCommandSetConfiguration CreateCommandSet()
        {
            if (_Table == null)
                _Table = new Table
                {
                    Name = Table,
                    TypeId = typeof(TInstance).Name,
                    Columns = _Mappings.Select(m => m.CreateColumn()).ToArray()
                };
            return new TableCommandSetConfiguration
            {
                TableSchema = _Table,
            };
        }

        private object GetObjectData(TInstance instance, string name, IRegistry registry)
        {
            IMapping<TInstance> mapping;
            if (_MappingsMap == null)
                _MappingsMap = _Mappings.ToDictionary(k => k.Column, StringComparer.OrdinalIgnoreCase);
            if (_MappingsMap.TryGetValue(name, out mapping))
                return mapping.GetInstanceValue(instance, registry);

            if (name == "orig__" + _PrimaryKey.Column)
                return _PrimaryKey.GetInstanceValue(instance, registry);
            else
                return Missing.Value; // MissingValue.Value;
        }

        private void ApplyObjectData(TypedRecord data, TInstance instance, IRegistry registry)
        {
            foreach (var mapping in _Mappings)
                mapping.SetInstanceValue(data, instance, registry);
        }

        private DelegateMapperConfiguration<TInstance> CreateMapper()
        {
            return new DelegateMapperConfiguration<TInstance>
            {
                ApplyObjectDataFunction = this.ApplyObjectData,
                GetObjectDataFunction = this.GetObjectData
            };
        }

        private DelegateDepotConfiguration<TInstance, TIdentifier> CreateDepot()
        {
            if (_PrimaryKey == null)
            {
                _PrimaryKey = _Mappings.OfType<PrimaryKeyMapping<TInstance, TIdentifier>>().FirstOrDefault();
                if (_PrimaryKey == null)
                    throw new ConfigurationException<TInstance>("Primary key is missing.");
            }
            var depot = new DelegateDepotConfiguration<TInstance, TIdentifier>
            {
                CreateInstanceFunction = Activator.CreateInstance<TInstance>,
                StoreIdentifierCopy = (instance, identifier) => _PrimaryKey.SetValue(instance, identifier),
                IdentifierGenerator = _PrimaryKey.Generator
            };
            return depot;
        }

        private IComponentConfiguration<IChangesTracker<TInstance>> CreateChangesTracker()
        {
            return new DelegateChangesTrackerConfiguration<TInstance, TIdentifier, int>
            {
                GetStateFunc = GetStateFunc
            };
        }

        private IComponentConfiguration<ICallbackManager<TInstance>> CreateCallbackManager()
        {
            var callbacks = new List<IComponentConfiguration<ICallbackManager<TInstance>>>();
            callbacks.Add(new DelegateCallbackManagerConfiguration<TInstance>
                        {
                            CallbackDelegates = new[] 
                            { 
                                new Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool>(CallbackFunction)
                            }
                        });
            callbacks.AddRange(_Callbacks);
            return new CompositeCallbackManagerConfiguration<TInstance>
            {
                Callbacks = callbacks
            };
        }

        private DelegateAttacherConfiguration<TInstance, TIdentifier> CreateAttacher()
        {
            if (_PrimaryKey == null)
            {
                _PrimaryKey = _Mappings.OfType<PrimaryKeyMapping<TInstance, TIdentifier>>().FirstOrDefault();
                if (_PrimaryKey == null)
                    throw new ConfigurationException<TInstance>("Primary key is missing.");
            }
            return new DelegateAttacherConfiguration<TInstance, TIdentifier>
            {
                ProvideAttachInfo = (instance, provider, attachInfo) =>
                    {
                        var id = _PrimaryKey.GetValue(instance);
                        attachInfo.Identifier = id;
                        attachInfo.IsInstanceNew = EqualityComparer<TIdentifier>.Default.Equals(id, default(TIdentifier));
                        attachInfo.GenerateNewIdentifier = true;
                        attachInfo.AreInstanceDataChanged = true;
                    }
            };
        }

        private bool CallbackFunction(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider)
        {
            bool result = true;
            foreach (var mapping in _Mappings)
                result = result && mapping.Callback(instance, operation, provider);
            return result;
        }

        private int GetStateFunc(TInstance instance)
        {
            unchecked
            {
                int checksum = 0;
                foreach (var item in _Mappings)
                {
                    checksum += item.GetInstanceHash(instance);
                }
                return checksum;
            }
        }

        #region Mappings Building

        /// <summary>
        /// Adds specified mapping to configuration.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> AddMapping(IMapping<TInstance> mapping)
        {
            if (Object.ReferenceEquals(mapping, null))
                throw new ArgumentNullException("mapping");
            _Mappings.Add(mapping);
            return this;
        }

        /// <summary>
        /// Adds primary key mapping.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapKey(
            string columName,
            Func<TInstance, TIdentifier> getValue,
            Action<TInstance, TIdentifier> setValue,
            IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> generator)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            if (Object.ReferenceEquals(generator, null))
                throw new ArgumentNullException("generator");
            return AddMapping(new PrimaryKeyMapping<TInstance, TIdentifier>(columName, getValue, setValue, generator));
        }

        /// <summary>
        /// Adds mapping to database generated primary key.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapDbGeneratedKey(
            string columName,
            Func<TInstance, TIdentifier> getValue,
            Action<TInstance, TIdentifier> setValue,
            IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> generator)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            if (Object.ReferenceEquals(generator, null))
                throw new ArgumentNullException("generator");
            return AddMapping(new PrimaryKeyMapping<TInstance, TIdentifier>(columName, getValue, setValue, generator) { IsDbGenerated = true });
        }

        /// <summary>
        /// Adds mapping to column stores scalar not-null value.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue,
            string errorMessage)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            return AddMapping(new ColumnMapping<TInstance, TValue>(columName, getValue, setValue, false, errorMessage));
        }

        /// <summary>
        /// Adds mapping to column stores scalar not-null value.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue)
        {
            return MapScalarValue<TValue>(columName, getValue, setValue, null);
        }

        /// <summary>
        /// Adds mapping to column stores scalar nullable value.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapNullableScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue,
            string errorMessage)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            return AddMapping(new ColumnMapping<TInstance, TValue>(columName, getValue, setValue, true, errorMessage));
        }

        /// <summary>
        /// Adds mapping to column stores scalar nullable value.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapNullableScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue)
        {
            return MapNullableScalarValue<TValue>(columName, getValue, setValue, null);
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue,
            string errorMessage)
            where TReference : class
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ArgumentNullException("columnName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            return AddMapping(new ReferenceMapping<TInstance, TReference, TReferenceIdentifier>(columnName, getValue, setValue, false, errorMessage));
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue)
            where TReference : class
        {
            return MapReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, null);
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier or null.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapNullableReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue,
            string errorMessage)
            where TReference : class
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ArgumentNullException("columnName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            return AddMapping(new ReferenceMapping<TInstance, TReference, TReferenceIdentifier>(columnName, getValue, setValue, true, errorMessage));
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier or null.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> MapNullableReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue)
            where TReference : class
        {
            return MapNullableReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, null);
        }

        /// <summary>
        /// Transforms the exception of specified type which message matches specified pattern to 
        /// exception with resulting message.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            TransformError<TException>(string pattern, MessageMatchType matchType, string resultingMessage)
            where TException : Exception
        {
            if (String.IsNullOrEmpty(resultingMessage))
                throw new ArgumentNullException("resultingMessage");
            if (_ErrorTransformer == null)
                _ErrorTransformer = new MatchErrorTransformerConfiguration<TInstance>();
            _ErrorTransformer.MatchCases.Add(new MatchCase(typeof(TException), pattern, matchType, resultingMessage));
            return this;
        }

        /// <summary>
        /// Adds delegate is called on specified persisting operation.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            Callback(Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool> callbackFunction)
        {
            if (Object.ReferenceEquals(callbackFunction, null))
                throw new ArgumentNullException("callbackFunction");

            _Callbacks.Add(new DelegateCallbackManagerConfiguration<TInstance>
                {
                    CallbackDelegates = new[] { callbackFunction }
                });
            return this;
        }

        /// <summary>
        /// Adds callback manager.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            WithCallbackManager(IComponentConfiguration<ICallbackManager<TInstance>> callbackManager)
        {
            if (Object.ReferenceEquals(callbackManager, null))
                throw new ArgumentNullException("callbackManager");
            _Callbacks.Add(callbackManager);
            return this;
        }

        /// <summary>
        /// Adds delegate is called on specified persisting operation.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier>
            Callback(PersistenceOperation operation, Func<TInstance, IPersistenceServiceProvider, bool> callbackFunction)
        {
            if (Object.ReferenceEquals(callbackFunction, null))
                throw new ArgumentNullException("callbackFunction");
            return Callback((instance, op, provider) =>
                                {
                                    if (op == operation)
                                        return callbackFunction(instance, provider);
                                    return true;
                                });
        }

        /// <summary>
        /// Configures the custom attacher for the instance. If you does not use this custom attacher,
        /// the repository will be configured with default by-identifier attacher.
        /// </summary>
        public AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryConfiguration<TInstance, TIdentifier>>.IdentifierProvider
            ConfigureAttacher()
        {
            var p = new AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryConfiguration<TInstance, TIdentifier>>.Parameters()
            {
                OnCompleted = a =>
                    {
                        _Attacher = a;
                        return this;
                    }
            };
            return new AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryConfiguration<TInstance, TIdentifier>>.IdentifierProvider(p);
        }

        /// <summary>
        /// Sets the attacher.
        /// </summary>
        public TableMappingRepositoryConfiguration<TInstance, TIdentifier> WithAttacher(IComponentConfiguration<IAttacher<TInstance, TIdentifier>> attacher)
        {
            if (Object.ReferenceEquals(attacher, null))
                throw new ArgumentNullException("attacher");
            _Attacher = attacher;
            return this;
        }

        #endregion
    }

    /// <summary>
    /// Base mapping functionallity.
    /// </summary>
    public interface IMapping<TInstance>
        where TInstance : class
    {
        /// <summary>
        /// Gets name of column where value of specified column is stored.
        /// </summary>
        string Column { get; }

        /// <summary>
        /// Gets the value for the column from the instance.
        /// </summary>
        object GetInstanceValue(TInstance instance, IRegistry registry);

        /// <summary>
        /// Sets the value from the column extracted from specified typed record to the instance.
        /// </summary>
        void SetInstanceValue(TypedRecord data, TInstance instance, IRegistry registry);

        /// <summary>
        /// Creates colum for the sql table.
        /// </summary>
        ColumnBase CreateColumn();

        /// <summary>
        /// Gets hash code of value of specified instance.
        /// </summary>
        int GetInstanceHash(TInstance instance);

        /// <summary>
        /// Processes callback from persistence system.
        /// </summary>
        bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider);
    }

    /// <summary>
    /// Implementation of <see cref="IMapping{TInstance}"/> interface with strongly typed value.
    /// </summary>
    public abstract class MappingBase<TInstance, TValue> : IMapping<TInstance>
        where TInstance : class
    {
        /// <summary>
        /// Initializes new instance of <see cref="MappingBase{TInstance, TValue}"/> type.
        /// </summary>
        public MappingBase(string column, string errorMessage, Func<TInstance, TValue> getValueFunc, Action<TInstance, TValue> setValueAction)
        {
            if (String.IsNullOrEmpty(column))
                throw new ArgumentNullException("column");
            Column = column;

            if (Object.ReferenceEquals(getValueFunc, null))
                throw new ArgumentNullException("getValueFunc");
            GetValue = getValueFunc;

            if (Object.ReferenceEquals(setValueAction, null))
                throw new ArgumentNullException("setValueAction");
            SetValue = setValueAction;

            ErrorMessage = errorMessage;
        }

        /// <summary>
        /// Gets name of column where value of specified column is stored.
        /// </summary>
        public string Column { get; private set; }

        /// <summary>
        /// Gets or sets message of the exception that is thrown when value provided for the parameter is invalid.
        /// </summary>
        public string ErrorMessage { get; private set; }

        /// <summary>
        /// Gets delegate that extracts value from an instance.
        /// </summary>
        public Func<TInstance, TValue> GetValue { get; private set; }

        /// <summary>
        /// Gets delegate that applies value to the instance.
        /// </summary>
        public Action<TInstance, TValue> SetValue { get; private set; }

        /// <summary>
        /// Gets the value for the column from the instance.
        /// </summary>
        public abstract object GetInstanceValue(TInstance instance, IRegistry registry);

        /// <summary>
        /// Sets the value from the column extracted from specified typed record to the instance.
        /// </summary>
        public abstract void SetInstanceValue(TypedRecord data, TInstance instance, IRegistry registry);

        /// <summary>
        /// Creates colum for the sql table.
        /// </summary>
        public abstract ColumnBase CreateColumn();

        /// <summary>
        /// Gets hash code of value of specified instance.
        /// </summary>
        public int GetInstanceHash(TInstance instance)
        {
            var value = GetValue(instance);
            if (ReferenceEquals(value, null))
                return 0;
            else
                return value.GetHashCode();
        }

        /// <summary>
        /// Processes callback from persistence system.
        /// </summary>
        public abstract bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider);
    }

    /// <summary>
    /// Maps scalar value to the table column.
    /// </summary>
    public class ColumnMapping<TInstance, TValue> : MappingBase<TInstance, TValue>
        where TInstance : class
    {
        /// <summary>
        /// Initializes new instance of <see cref="ColumnMapping{TInstance, TValue}"/> type.
        /// </summary>
        public ColumnMapping(string column, Func<TInstance, TValue> getValueFunc, Action<TInstance, TValue> setValueAction, bool allowsNull,
            string errorMessage)
            : base(column, errorMessage, getValueFunc, setValueAction)
        {
            AllowsNull = allowsNull;
        }

        /// <summary>
        /// Determines whether column value allows null.
        /// </summary>
        public bool AllowsNull { get; private set; }

        /// <summary>
        /// Gets the value for the column from the instance.
        /// </summary>
        public override object GetInstanceValue(TInstance instance, IRegistry registry)
        {
            return GetValue(instance);
        }

        /// <summary>
        /// Sets the value from the column extracted from specified typed record to the instance.
        /// </summary>
        public override void SetInstanceValue(TypedRecord data, TInstance instance, IRegistry registry)
        {
            if (data.ContainsName(Column))
                SetValue(instance, (TValue)data.GetValue(Column));
        }

        /// <summary>
        /// Creates colum for the sql table.
        /// </summary>
        public override ColumnBase CreateColumn()
        {
            return new Column<TValue> { AllowNull = AllowsNull, Name = Column, ErrorMessage = ErrorMessage };
        }

        /// <summary>
        /// Processes callback from persistence system.
        /// </summary>
        public override bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider)
        {
            return true;
        }
    }

    /// <summary>
    /// Maps value that is reference to another object to the column of the table.
    /// </summary>
    public class ReferenceMapping<TInstance, TReference, TReferenceIdentifier> : MappingBase<TInstance, TReference>
        where TInstance : class
        where TReference : class
    {
        /// <summary>
        /// Initializes new instance of <see cref="ReferenceMapping{TInstance, TReference, TReferenceIdentifier}"/> type.
        /// </summary>
        public ReferenceMapping(string column, Func<TInstance, TReference> getValueFunc, Action<TInstance, TReference> setValueAction, bool allowsNull, string errorMessage)
            : base(column, errorMessage, getValueFunc, setValueAction)
        {
            AllowsNull = allowsNull;
        }

        /// <summary>
        /// Determines whether column value allows null.
        /// </summary>
        public bool AllowsNull { get; private set; }

        /// <summary>
        /// Gets the value for the column from the instance.
        /// </summary>
        public override object GetInstanceValue(TInstance instance, IRegistry registry)
        {
            var reference = GetValue(instance);
            if (ReferenceEquals(reference, null))
                return null;
            return registry.GetIdentifier<TReference, TReferenceIdentifier>(reference);
        }

        /// <summary>
        /// Sets the value from the column extracted from specified typed record to the instance.
        /// </summary>
        public override void SetInstanceValue(TypedRecord data, TInstance instance, IRegistry registry)
        {
            if (data.ContainsName(Column))
            {
                var refId = data.GetValue(Column);
                if (ReferenceEquals(refId, null))
                    SetValue(instance, null);
                else
                {
                    var refInstance = registry.GetInstance<TReference, TReferenceIdentifier>((TReferenceIdentifier)refId);
                    SetValue(instance, refInstance);
                }
            }
        }

        /// <summary>
        /// Creates colum for the sql table.
        /// </summary>
        public override ColumnBase CreateColumn()
        {
            return new Column<TReferenceIdentifier> { AllowNull = AllowsNull, Name = Column, ErrorMessage = ErrorMessage };
        }

        /// <summary>
        /// Processes callback from persistence system.
        /// </summary>
        public override bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider)
        {
            return true;
        }
    }

    /// <summary>
    /// Maps primary key column to specified instance.
    /// </summary>
    public class PrimaryKeyMapping<TInstance, TValue> : MappingBase<TInstance, TValue>
        where TInstance : class
    {
        /// <summary>
        /// Initializes new instance of <see cref="PrimaryKeyMapping{TInstance, TValue}"/> type.
        /// </summary>
        public PrimaryKeyMapping(
            string column,
            Func<TInstance, TValue> getValueFunc,
            Action<TInstance, TValue> setValueAction,
            IComponentConfiguration<IIdentifierGenerator<TInstance, TValue>> generator)
            : base(column, null, getValueFunc, setValueAction)
        {
            if (Object.ReferenceEquals(generator, null))
                throw new ArgumentNullException("generator");
            Generator = generator;
        }

        /// <summary>
        /// Gets or sets a value indicating whether value for primary key is generated by database due inserting.
        /// </summary>
        public bool IsDbGenerated { get; set; }

        /// <summary>
        /// Gets identifier generator that generates sequence of new values for primary key.
        /// </summary>
        public IComponentConfiguration<IIdentifierGenerator<TInstance, TValue>> Generator { get; private set; }

        /// <summary>
        /// Gets the value for the column from the instance.
        /// </summary>
        public override object GetInstanceValue(TInstance instance, IRegistry registry)
        {
            return registry.GetIdentifier<TInstance, TValue>(instance);
        }

        /// <summary>
        /// Sets the value from the column extracted from specified typed record to the instance.
        /// </summary>
        public override void SetInstanceValue(TypedRecord data, TInstance instance, IRegistry registry)
        {
            if (IsDbGenerated)
            {
                var newIdName = "new__" + Column;
                if (data.ContainsName(newIdName))
                {
                    var newIdentifier = (TValue)Convert.ChangeType(data.GetValue(newIdName), typeof(TValue), CultureInfo.CurrentCulture);
                    var oldId = registry.GetIdentifier<TInstance, TValue>(instance);
                    if (!Equals(newIdName, oldId))
                        registry.PutNewIdentifier(instance, newIdentifier);
                }
            }
        }

        /// <summary>
        /// Creates instance of <see cref="ColumnBase"/> for the column.
        /// </summary>
        public override ColumnBase CreateColumn()
        {
            return new Key<TValue> { AllowNull = false, Name = Column, ErrorMessage = ErrorMessage, IsDatabaseGenerated = IsDbGenerated };
        }

        /// <summary>
        /// Processes callback from persistence system.
        /// </summary>
        public override bool Callback(TInstance instance, PersistenceOperation operation, IPersistenceServiceProvider provider)
        {
            return true;
        }
    }
}
